package Mips;

import Tree.*;
import Temp.*;
import Temp.LabelList;
import java.util.List;
import java.util.LinkedList;
import java.util.Hashtable;

public class Codegen {
	MipsFrame frame;

	static final String MOVE_INST = "move `d0, `s0";

	static final String LOADI_INST = "li `d0, ";

	static final String LOADW_INST = "lw `d0, ";

	static final String STOREW_INST = "sw `s0, ";

	public Codegen(MipsFrame f) {
		frame = f;
	}

	static Assem.Instr OPER(String a, TempList d, TempList s) {
		return new Assem.OPER(a, d, s);
	}

	private static Tree.CONST CONST16(Tree.Exp e) {
		if (e instanceof Tree.CONST) {
			Tree.CONST c = (Tree.CONST) e;
			int value = c.value;
			if (value == (short) value)
				return c;
		}
		return null;
	}

	private Assem.InstrList ilist = null, last = null;

	private void emit(Assem.Instr inst) {
		if (last != null)
			last = last.tail = new Assem.InstrList(inst, null);
		else {
			if (ilist != null)
				throw new Error("Codegen.emit");
			last = ilist = new Assem.InstrList(inst, null);
		}
	}

	Assem.InstrList codegen(Tree.Stm s) {
		munchStm(s);
		Assem.InstrList l = ilist;
		ilist = last = null;
		return l;
	}

	static Assem.Instr MOVE(String a, Temp d, Temp s) {
		return new Assem.MOVE(a, d, s);
	}

	static TempList L(Temp h) {
		return new TempList(h, null);
	}

	static TempList L(Temp h, TempList t) {
		return new TempList(h, t);
	}

	public void munchStms(Tree.StmList slist) {
		Tree.StmList list = slist;
		for (; list != null; list = list.tail) {
			munchStm(list.head);
		}
	}

	void munchStm(Tree.Stm s) {
		if (s instanceof Tree.MOVE)
			munchStm((Tree.MOVE) s);
		else if (s instanceof Tree.EXPR)
			munchStm((Tree.EXPR) s);
		else if (s instanceof Tree.JUMP)
			munchStm((Tree.JUMP) s);
		else if (s instanceof Tree.CJUMP)
			munchStm((Tree.CJUMP) s);
		else if (s instanceof Tree.LABEL)
			munchStm((Tree.LABEL) s);
		else
			throw new Error("Codegen.munchStm");
	}

	void munchStm(Tree.MOVE s) {
		if (s.dst instanceof Tree.TEMP) {
			Temp dest = ((Tree.TEMP) s.dst).temp;
			emit(OPER("add `d0,`s0,$0", L(dest),
					L(munchExp(s.src))));
			return;
		}
		if (s.dst instanceof MEM) {
			Exp memDst = ((MEM) s.dst).exp;
			if (memDst instanceof CONST) {
				emit(OPER("sw `s1,0(`s0)", null, L(
						munchExp(s.dst), L(munchExp(s.src), null))));
				return;
			}
			if (memDst instanceof BINOP && ((BINOP) memDst).binop == 0) {
				BINOP b = (BINOP) memDst;
				if (b.left instanceof CONST) {
					emit(OPER("sw `s1," + ((CONST) b.left).value
							+ "(`s0)", null, L(munchExp(b.right), L(
							munchExp(s.src)))));
					return;
				}
				if (b.right instanceof CONST) {
					emit(OPER("sw `s1,"
							+ ((CONST) b.right).value + "(`s0)", null, L(
							munchExp(b.left), L(munchExp(s.src)))));
					return;
				}
			}
			emit(OPER("sw `s1,0(`s0)", null, L(
					munchExp(s.dst), L(munchExp(s.src), null))));
			return;
		}
		throw new Error("move node has illegal destination type");
	}

	void munchStm(Tree.EXPR s) {
		munchExp(s.exp);
	}

	void munchStm(Tree.JUMP s) {
		if (s.exp instanceof Tree.NAME) {
			Tree.NAME name = (Tree.NAME) s.exp;
			Label label = name.label;
			emit(new Assem.OPER("j `j0", null, null, s.targets));
		} else {
			throw new Error("can't JUMP to a non-label");
		}
	}

	private static String[] CJUMP = new String[10];
	static {
		CJUMP[Tree.CJUMP.EQ] = "beq";
		CJUMP[Tree.CJUMP.NE] = "bne";
		CJUMP[Tree.CJUMP.LT] = "blt";
		CJUMP[Tree.CJUMP.GT] = "bgt";
		CJUMP[Tree.CJUMP.LE] = "ble";
		CJUMP[Tree.CJUMP.GE] = "bge";
		CJUMP[Tree.CJUMP.ULT] = "bltu";
		CJUMP[Tree.CJUMP.ULE] = "bleu";
		CJUMP[Tree.CJUMP.UGT] = "bgtu";
		CJUMP[Tree.CJUMP.UGE] = "bgeu";
	}

	public void visit(CJUMP n) {

											// targets
	}

	void munchStm(Tree.LABEL l) {
		emit(new Assem.LABEL(l.label.toString() + ":", l.label));
	}

	Temp munchExp(Tree.Exp s) {
		if (s instanceof Tree.CONST)
			return munchExp((Tree.CONST) s);
		else if (s instanceof Tree.NAME)
			return munchExp((Tree.NAME) s);
		else if (s instanceof Tree.TEMP)
			return munchExp((Tree.TEMP) s);
		else if (s instanceof Tree.BINOP)
			return munchExp((Tree.BINOP) s);
		else if (s instanceof Tree.MEM)
			return munchExp((Tree.MEM) s);
		else if (s instanceof Tree.CALL)
			return munchExp((Tree.CALL) s);
		else
			throw new Error("Codegen.munchExp");
	}

	Temp munchExp(Tree.CONST e) {
		Temp r = new Temp();
		emit(OPER("addi `d0,$0," + e.value, L(r), null));
		return r;
	}

	Temp munchExp(Tree.NAME e) {
		Temp r = new Temp();
		emit(OPER("la `d0," + e.label.toString() + "", L(r), null));
		return r;
	}

	Temp munchExp(Tree.TEMP e) {
		if (e.temp == frame.FP) {
			Temp t = new Temp();
			emit(OPER("addu `d0,`s0," + frame.name + "_framesize", L(t),
					L(frame.SP)));
			return t;
		}
		return e.temp;
	}

	private static String[] BINOP = new String[10];
	static {
		BINOP[Tree.BINOP.PLUS] = "add";
		BINOP[Tree.BINOP.MINUS] = "sub";
		BINOP[Tree.BINOP.MUL] = "mulo";
		BINOP[Tree.BINOP.DIV] = "div";
		BINOP[Tree.BINOP.AND] = "and";
		BINOP[Tree.BINOP.OR] = "or";
		BINOP[Tree.BINOP.LSHIFT] = "sll";
		BINOP[Tree.BINOP.RSHIFT] = "srl";
		BINOP[Tree.BINOP.ARSHIFT] = "sra";
		BINOP[Tree.BINOP.XOR] = "xor";
	}

	private static int shift(int i) {
		int shift = 0;
		if ((i >= 2) && ((i & (i - 1)) == 0)) {
			while (i > 1) {
				shift += 1;
				i >>= 1;
			}
		}
		return shift;
	}

	Temp munchExp(Tree.BINOP e) {
		Temp r = new Temp();
		if (e.binop == 0) {
			if (e.left instanceof CONST) {
				emit(OPER("addi `d0,`s0," + ((CONST) e.left).value + "", L(r),
						L(munchExp(e.right))));
				return r;
			}
			if (e.right instanceof CONST) {
				emit(OPER("addi `d0,`s0," + ((CONST) e.right).value + "", L(r),
						L(munchExp(e.left))));
				return r;
			}
			emit(OPER("add `d0,`s0,`s1", L(r), L(munchExp(e.left),
					L(munchExp(e.right)))));
			return r;
		}
		if (e.binop == 1) {
			if (e.left instanceof CONST) {
				emit(OPER("subi `d0," + ((CONST) e.left).value + ",`s0", L(r),
						L(munchExp(e.right))));
				return r;
			}
			if (e.right instanceof CONST) {
				emit(OPER("subi `d0,`s0," + ((CONST) e.right).value + "", L(r),
						L(munchExp(e.left))));
				return r;
			}
			emit(OPER("sub `d0,`s0,`s1", L(r), L(munchExp(e.left),
					L(munchExp(e.right)))));
			return r;
		}
		emit(OPER(BINOP[e.binop] + " `d0,`s0,`s1", L(r), L(
				munchExp(e.left), L(munchExp(e.right)))));
		return r;
	}

	Temp munchExp(Tree.MEM e) {
		Temp r = new Temp();
		if (e.exp instanceof CONST) {
			emit(OPER("lw `d0," + ((CONST) e.exp).value
					+ "(`s0)", L(r), null));
			return r;
		}
		if (e.exp instanceof BINOP && ((BINOP) e.exp).binop == 0) {
			BINOP b = (BINOP) e.exp;
			if (b.left instanceof CONST) {
				emit(OPER("lw `d0,"
						+ ((CONST) b.left).value + "(`s0)", L(r),
						L(munchExp(b.right))));
			}
			if (b.right instanceof CONST) {
				emit(OPER("lw `d0,"
						+ ((CONST) b.right).value + "(`s0)", L(r),
						L(munchExp(b.left))));
			}
			return r;
		}
		emit(OPER("lw `d0,0(`s0)", L(r), L(munchExp(e.exp))));
		return r;
	}

	Temp munchExp(Tree.CALL s) {
		if (!(s.func instanceof Tree.NAME))
			throw new Error("Method name is not a NAME");
		TempList argTemps = munchArgs(0, s.args);
		emit(new Assem.OPER("jal " + ((Tree.NAME) s.func).label,
				MipsFrame.calldefs, L(munchExp(s.func), argTemps)));
		return MipsFrame.V0;

	}

	private TempList munchArgs(int i, Tree.ExpList args) {
		if (args == null)
			return null;
		Temp src = munchExp(args.head);
		if (i > frame.maxArgs)
			frame.maxArgs = i;
		switch (i) {
		case 0:
			emit(MOVE("move `d0,`s0", frame.A0, src));
			break;
		case 1:
			emit(MOVE("move `d0,`s0", frame.A1, src));
			break;
		case 2:
			emit(MOVE("move `d0,`s0", frame.A2, src));
			break;
		case 3:
			emit(MOVE("move `d0,`s0", frame.A3, src));
			break;
		default:
			emit(OPER("sw `s0" + (i - 1) * frame.wordSize() + "(`s1)", null,
					L(src, L(frame.SP))));
			break;
		}
		return L(src, munchArgs(i + 1, args.tail));
	}

	public Assem.InstrList getBareResult() {
		return ilist.reverse();
	}

	public String format(Assem.InstrList is, TempMap f) {
		String s = "";
		s = is.head.toString() + "";
		System.out.println(is.head);
		System.out.println(is.head.format(f));
		if (is.tail == null) {
			return s + is.head.format(f);
		} else {
			return s + is.head.format(f) + "" + format(is.tail, f);
		}
	}
}
